package summer.codegenerator.persistence.hibernate.base;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import summer.codegenerator.model.base.Duplicate;
import summer.codegenerator.persistence.hibernate.base.tool.Pagination;
import summer.codegenerator.persistence.hibernate.base.tool.Sort;
import summer.codegenerator.persistence.hibernate.base.tool.WhereClause;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import summer.codegenerator.common.util.ClazzUtil;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import javax.persistence.Id;

/**
 * @author 蒋振
 *
 * @param <T>
 *            持久化的目标泛型类
 */
@Component
public class BaseDaoImpl<T> implements BaseDao<T> {

	@Autowired
	private SessionFactory sessionFactory;
	
	// 持久化目标类
	private Class<?> persistClass;

	// 继承类才能获取持久类简称
	protected String getPersistClassSimpleName() {
		return this.persistClass.getSimpleName();
	}
	
	// 继承类才能获取Session
	protected Session getCurrentSession() {
		return this.sessionFactory.getCurrentSession();
	}

	public BaseDaoImpl() {
		Type type = this.getClass().getGenericSuperclass();
		if (type instanceof ParameterizedType) {
			ParameterizedType pt = (ParameterizedType) type;
			Type[] params = pt.getActualTypeArguments();
			this.persistClass = (Class<T>) params[0];
		}
	}
	
	@Override
	public Class<?> getPersistClass() {
		return this.persistClass;
	}

	@Override
	public void saveCheck(T t) {

	}

	@Override
	public T findById(Long id) {
		T obj = (T) this.getCurrentSession().get(persistClass, id);
		return obj;
	}

	@Override
	public T copyById(Long id) {
		T obj = this.findById(id);
		obj = this.dto(obj);
		return obj;
	}

	@Override
	public void save(T entity) {
		this.getCurrentSession().save(entity);
	}

	@Override
	public void update(T entity) {
		this.getCurrentSession().update(entity);
	}

	@Override
	public void delete(T entity) {
		this.getCurrentSession().delete(entity);
	}

	@Override
	public void delete(Long entityId) {
		final String hql = "delete from " + this.getPersistClassSimpleName() + " where id = :entityId";
		this.getCurrentSession().createQuery(hql).setParameter("entityId", entityId).executeUpdate();
	}

	@Override
	public List<T> list() {
		return this.list(null, null, null);
	}

	@Override
	public List<T> list(WhereClause condition, Sort sort) {
		return this.list(condition, sort, null);
	}

	@Override
	public List<T> list(WhereClause condition, Sort sort, Pagination pagination) {
		StringBuilder hql = new StringBuilder();
		hql.append("from " + this.getPersistClassSimpleName());
		if (condition != null) {
			hql.append(condition.getClause());
		}
		if (sort != null) {
			hql.append(sort.getClause());
		}

		Query q = this.getCurrentSession().createQuery(hql.toString());
		if(condition != null) {
			Map<String, Object> paramMap = condition.getParamMap();
			for(String paramName : paramMap.keySet()) {
				q.setParameter(paramName, paramMap.get(paramName));
			}
		}
		if (pagination != null) {
			q.setFirstResult(pagination.first());
			q.setMaxResults(pagination.max());
		}
		return q.list();
	}

	@Override
	public List<T> listByCriteria(JSONObject condition, List<Order> orders, Pagination pagination) {
		Criteria criteria = this.getCurrentSession().createCriteria(persistClass);
		// 条件
		Criterion criterion = this.addRestrictions(condition);
		if (criterion != null) {
			criteria.add(criterion);
		}
		// 分页
		if (pagination != null) {
			criteria.setFirstResult(pagination.first());
			criteria.setMaxResults(pagination.max());
		}
		// 排序
		if (orders != null && orders.size() > 0) {
			for (Order order : orders) {
				criteria.addOrder(order);
			}
		}
		List<T> data = criteria.list();
		data = dtos(data);
		return data;
	}

	@Override
	public Long countTotal(WhereClause condition) {
		StringBuilder hql = new StringBuilder();
		hql.append("select count(*) from " + this.getPersistClassSimpleName());
		if (condition != null) {
			hql.append(condition.getClause());
		}
		Query q = this.getCurrentSession().createQuery(hql.toString());
		return (Long) q.uniqueResult();
	}

	@Override
	public Long countTotalByCriteria(JSONObject condition) {
		Criteria criteria = this.getCurrentSession().createCriteria(persistClass);
		Criterion criterion = this.addRestrictions(condition);
		if (criterion != null) {
			criteria.add(criterion);
		}
		criteria.setProjection(Projections.rowCount());
		return (Long) criteria.uniqueResult();
	}

	@Override
	public void flush() {
		this.getCurrentSession().flush();
	}

	@Override
	public void clear() {
		this.getCurrentSession().clear();
	}

	@Override
	public void flushThenClear() {
		this.getCurrentSession().flush();
		this.getCurrentSession().clear();
	}

	/**
	 * 复制一份全新的数据
	 * 用于传输
	 * @param sources 源数据
	 * @return
	 */
	@Override
	public List<T> dtos(List<T> sources) {
		List<T> dtos = new LinkedList<T>();
		if (!CollectionUtils.isEmpty(sources)) {
			for (T source : sources) {
				T dto = dto(source);
				dtos.add(dto);
			}
		}
		return dtos;
	}
	
	@Override
	public T dto(T source) {
		return (T)Duplicate.dp(source);
	}

	private Class<?> getFieldType(Class<?> clazz, Iterator<String> it) {
		Class<?> fieldType = null;
		try {
			String propertyName = it.next();
			Field field = clazz.getDeclaredField(propertyName);
			fieldType = field.getType();
			if (it.hasNext()) {
				fieldType = this.getFieldType(fieldType, it);
			}
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		}
		return fieldType;
	}

	/**
	 * 根据Java模型的属性类型获取正确的前端传参
	 *
	 * 
	 * @param obj
	 * @param propertyName
	 * @return
	 * @author: jiangz
	 * @date: Created on 2019年7月22日 上午9:15:38
	 */
	private Object typedObject(JSONObject obj, String propertyName) {
		List<String> propertyNames = Arrays.asList(propertyName.split("\\."));
		Iterator<String> it = propertyNames.iterator();
		// 需要到Clazz中找到字段对应的类型
		// 不支持 char Character
		Class<?> fieldType = this.getFieldType(this.persistClass, it);

		Object value = null;

		if (fieldType.isEnum()) {
			Integer enumIndex = obj.getInteger(this.OPERAND);
			value = ClazzUtil.getEnumObjByClassObjAndEnumIndex(fieldType, enumIndex);
		} else if (fieldType.isPrimitive()) {
			String primitiveTypeName = fieldType.getName();
			if (Objects.equals(primitiveTypeName, "byte")) {
				value = obj.getByteValue(this.OPERAND);
			} else if (Objects.equals(primitiveTypeName, "short")) {
				value = obj.getShortValue(this.OPERAND);
			} else if (Objects.equals(primitiveTypeName, "int")) {
				value = obj.getIntValue(this.OPERAND);
			} else if (Objects.equals(primitiveTypeName, "long")) {
				value = obj.getLongValue(this.OPERAND);
			} else if (Objects.equals(primitiveTypeName, "boolean")) {
				value = obj.getBooleanValue(this.OPERAND);
			} else if (Objects.equals(primitiveTypeName, "float")) {
				value = obj.getFloatValue(this.OPERAND);
			} else if (Objects.equals(primitiveTypeName, "double")) {
				value = obj.getDouble(this.OPERAND);
			}
		} else if (fieldType.isAssignableFrom(Byte.class)) {
			value = obj.getByte(this.OPERAND);
		} else if (fieldType.isAssignableFrom(Short.class)) {
			value = obj.getShort(this.OPERAND);
		} else if (fieldType.isAssignableFrom(Integer.class)) {
			value = obj.getInteger(this.OPERAND);
		} else if (fieldType.isAssignableFrom(Long.class)) {
			value = obj.getLong(this.OPERAND);
		} else if (fieldType.isAssignableFrom(Boolean.class)) {
			value = obj.getBoolean(this.OPERAND);
		} else if (fieldType.isAssignableFrom(Float.class)) {
			value = obj.getFloat(this.OPERAND);
		} else if (fieldType.isAssignableFrom(Double.class)) {
			value = obj.getDouble(this.OPERAND);
		} else {
			value = obj.get(this.OPERAND);
		}
		return value;
	}

	private final String GROUP_LOGIC = "logic";

	private final String PROPERTY_NAME = "propertyName";

	private final String OPERATOR = "operator";

	private final String OPERAND = "operand";

	private Criterion addRestrictions(JSONObject condition) {
		Criterion criterion = null;
		if (condition != null) {
			JSONObject jsonObj = (JSONObject) condition;

			// 条件组属性
			String logic = jsonObj.getString(this.GROUP_LOGIC);

			// 条件属性
			// 其中jsonObj.get("operand")
			// 需要根据Java Model Field获取其值
			String propertyName = jsonObj.getString(this.PROPERTY_NAME);
			String operator = jsonObj.getString(this.OPERATOR);

			if (StringUtils.isNoneBlank(propertyName, operator)) {
				Object operand = this.typedObject(jsonObj, propertyName);
				// 自身是条件
				if ("=".equals(operator)) {
					criterion = Restrictions.eq(propertyName, operand);
				} else if ("!=".equals(operator)) {
					criterion = Restrictions.ne(propertyName, operand);
				} else if ("like".equals(operator)) {
					criterion = Restrictions.like(propertyName, operand);
				} else if ("<".equals(operator)) {
					criterion = Restrictions.lt(propertyName, operand);
				} else if ("<=".equals(operator)) {
					criterion = Restrictions.le(propertyName, operand);
				} else if (">".equals(operator)) {
					criterion = Restrictions.gt(propertyName, operand);
				} else if (">=".equals(operator)) {
					criterion = Restrictions.ge(propertyName, operand);
				} else if ("is null".equals(operator)) {
					criterion = Restrictions.isNull(propertyName);
				} else if ("is not null".equals(operator)) {
					criterion = Restrictions.isNotNull(propertyName);
				}
			} else if (StringUtils.isNoneBlank(logic)) {
				// 条件组
				JSONArray subconditions = jsonObj.getJSONArray("conditions");
				if (subconditions != null && subconditions.size() > 0) {
					int subSize = subconditions.size();
					Criterion[] subcriterions = new Criterion[subSize];
					for (int i = 0; i < subSize; i++) {
						JSONObject subcondition = subconditions.getJSONObject(i);
						Criterion subcriterion = this.addRestrictions(subcondition);
						subcriterions[i] = subcriterion;
					}
					if ("and".equals(logic)) {
						criterion = Restrictions.and(subcriterions);
					} else if ("or".equals(logic)) {
						criterion = Restrictions.or(subcriterions);
					}
				}
			}
		}
		return criterion;
	}
}
